Published: Sat, Aug 15, 2026
Jool at BIND9
And not, drool at 69! (hehe, boi!) Guess what? I finally did it! For the “first” time, got my hands dirty setting up a functional DNS64/NAT64 translation in a “futile” attempt to build a forwarding DNS server with support for DoT/DoH. But, all of this! why? my dear ISP! (mut̄həfu**er!!) So, buckle up, as I shit over here!
NAT64
Let’s get the “translation” done right! For quite sometime, I had to scratch my head really hard to figure it out! Like, how the fuck on earth can one get the damn WKP (64:ff9b::/96) working from a custom setup?
-
Add a static route (
ip route add 64:ff9b::/96 via 2001:db8::1) from every IPv6-only client machine? -
Run it in a BGP environment? (“NAT64 is a pretty clever way to add IPv4 connectivity to an IPv6-only network, since it takes advantage of the fact that the entire IPv4 address space can actually be encoded in half of a single subnet of IPv6”, the best explanation ever
!) -
Deploy at ISP scale? (sadly, I don’t own my ISP!)
To quote level66, “To be able to use the translation mechanism across multiple networks, we are using a IPv6 Global Unicast prefix as the NAT64 prefix”, I realized the need to use an NSP, and with Google Cloud, I can get a “publicly advertised” External IP address; even better, “A /96 is allocated to the VM on creation” when using Global Unicast Addresses (GUA) – hell yeah!
Break a Sweat!
VPC and VM configurations are similar to that of DVWA, i.e., an IP forwarding set combo of IPv4-only with IPv4/IPv6 (dual-stack, with an external IPv6 access type). Time to hangout with jool!
-
Install: Consulting the official documentation alongside How to Configure NAT64 with Jool on Linux, this seemed to work:
#!/bin/bash sudo apt install linux-headers-$(uname -r) sudo apt update && sudo apt install jool-dkms jool-tools # Load the Jool NAT64 kernel module sudo modprobe jool # To load automatically at boot echo "jool" | sudo tee -a /etc/modules-load.d/jool.conf # Verify the module is loaded lsmod | grep jool -
Pool: First, know your
/96-prefix allocated tonic1(ens5) asinet6 <IPv6_address_on_ens5>/128 scope global dynamic noprefixroutevia:#!/bin/bash ip a-
Next, on my way to set up, I was greeted with
Error: Netfilter is the only available instance framework., the moment I ran:#!/bin/bash sudo jool instance add "nat64" --iptables --pool6 <IPv6_address_on_ens5>/96Wow! looks like, netfilter was the only choice! However, since Netfilter is “greedy” in that, “Jool attempts to translate everything, and the rest of the network subsystem gets the leftovers”, I was relieved of the burden to configure
iptables/ip6tablesto direct traffic to Jool, phew
!#!/bin/bash sudo jool instance add "nat64" --netfilter <IPv6_address_on_ens5>/96 # Verify sudo jool -i nat64 global display | grep pool6 -
Moving on to
pool4– Jool’s IPv4 transport address pool, I felt an itch to use tun2socks with WARP instead of sacrificing the IPv4 address onens4(nic0). Adopted everything but thefwmarkand WireGuard stuff from DVWA:#!/bin/bash tun2socks --device tun0 --proxy socks5://127.0.0.1:40000 --interface ens4 sudo ip route add default via <IPv4_gateway_on_tun0> dev tun0 metric <METRIC> table <ROUTE_TABLE> sudo ip rule add from <IPv4_network_on_tun0> table <ROUTE_TABLE> # With <IPv4_gateway_on_tun0> = <IPv4_inet_on_tun0> sudo jool -i nat64 pool4 add --icmp <IPv4_inet_on_tun0> 61001-65535 sudo jool -i nat64 pool4 add --tcp <IPv4_inet_on_tun0> 61001-65535 sudo jool -i nat64 pool4 add --udp <IPv4_inet_on_tun0> 61001-65535 # Verify sudo jool -i nat64 pool4 display # --tcp (default) sudo jool -i nat64 pool4 display --udp sudo jool -i nat64 pool4 display --icmpAnd, it worked
! -
Don’t forget to enable IP forwarding!
#!/bin/bash # ufw (if) sudo sed -i "/net\/ipv4\/ip_forward/s/#//" /etc/ufw/sysctl.conf sudo sed -i "/net\/ipv6\/conf\/all\/forwarding/s/#//" /etc/ufw/sysctl.conf # Verify (if ufw) cat /etc/default/ufw | grep SYSCTL cat /etc/ufw/sysctl.conf | grep ip_forward cat /etc/ufw/sysctl.conf | grep ipv6/conf/all/forwarding # sysctl echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf echo "net.ipv6.conf.all.forwarding=1" | sudo tee -a /etc/sysctl.conf sudo sysctl -p # Verify sudo sysctl -a | grep ip_forward sudo sysctl -a | grep ipv6.conf.all.forwarding
-
-
Firewall: With
ufw, while saving yourssh’s ass!#!/bin/bash # ssh sudo ufw allow proto tcp from 0.0.0.0/0 to any port 22 # nat64 sudo ufw allow proto tcp from ::/0 to any sudo ufw allow proto udp from ::/0 to any # Verify sudo ufw status sudo ufw show added-
Note: In your VPC Network → Firewall, add an equivalent set of rules or, FAFO
!
-
Note: In your VPC Network → Firewall, add an equivalent set of rules or, FAFO
DNS
A “short” journey from obtaining TLS certificates to implementation of DNS64, coupled with encrypted channels at both the receiving and forwarding ends under identical VPC and VM configurations, if you aren’t bored enough!
Get Certified
A painstaking task of requesting one using a Public CA and an ACME client
- Grant the Public CA External Account Key Creator (
roles/publicca.externalAccountKeyCreator) IAM role-
Cloud Console → IAM & Admin → Service accounts → Create service account → Permissions → Select a role → External Account Key Creator (Beta)
-
Take note of the Principal after account creation
-
- Enable the Public CA API
- Cloud Console → APIs & Services → Enable APIs and services → Public Certificate Authority API
- Request an EAB key ID and HMAC
-
Cloud Console → Activate Cloud Shell (G then S)
gcloud publicca external-account-keys create
-
-
Fire up your VM, install
certbot, run apache2, and then, register an ACME account:#!/bin/bash sudo certbot register \ --email "EMAIL_ADDRESS" \ --no-eff-email \ --server "SERVER" \ --eab-kid "EAB_KID" \ --eab-hmac-key "EAB_HMAC_KEY" # Verify sudo certbot show_account --server "SERVER"-
EMAIL_ADDRESS: the Principal’s email address -
SERVER: ACME directory URL for the production environment (https://dv.acme-v02.api.pki.goog/directory)
-
-
Request certificates
#!/bin/bash sudo certbot certonly \ --manual \ --server "SERVER" \ --domains "DOMAINS" # Verify sudo ls -lah /etc/letsencrypt/live sudo ls -lah /etc/letsencrypt/archive -
Automatic renewal: The AppArmor technique mentioned in Mastering DNS Privacy or DNS Over HTTPS for “recursive read-only access to all files and folders in
/etc/letsencrypt” didn’t work
! Having recourse to M9A’s answer, I had to chalk out a way:#!/bin/bash CERT_DIR="<DOMAINS>" # CACHE_DIR="/var/cache/bind" sudo cp -ru "/etc/letsencrypt/archive/$CERT_DIR" "$CACHE_DIR" sudo chown -R bind:bind "$CACHE_DIR/$CERT_DIR"Finally, a cron job! (no, not b***job!)
#!/bin/bash echo "0 0,12 * * * root /opt/certbot/bin/python -c 'import random; import time; time.sleep(random.random() * 3600)' && sudo certbot renew -q && bash <path_to_shell_script>" | sudo tee -a /etc/crontab # [Monthly] Upgrade certbot echo "0 1 1 * * root /opt/certbot/bin/pip install --upgrade --quiet pip certbot certbot-apache" | sudo tee -a /etc/crontab
Look Up
-
Configure DNS64 with a recursive resolver whilst using systemd-resolved alongside BIND and DNS over TLS in the
forwarders:#/etc/bind/named.conf.options acl "RFC1918" { 0.0.0.0/8; 10.0.0.0/8; 172.16.0.0/12; 192.168.0.0/16; }; tls cloudflare-tls { remote-hostname "one.one.one.one"; }; tls google-tls { remote-hostname "dns.google"; }; tls local-tls { key-file "/var/cache/bind/<DOMAINS>/privkey.pem"; cert-file "/var/cache/bind/<DOMAINS>/fullchain.pem"; }; options { directory "/var/cache/bind"; allow-query { any; }; listen-on port 53 { <IPv4_address_on_ens4>; }; listen-on port 853 tls local-tls { any; }; listen-on port 443 tls local-tls http default { any; }; listen-on-v6 port 53 { <IPv6_address_on_ens5>; }; listen-on-v6 port 853 tls local-tls { any; }; listen-on-v6 port 443 tls local-tls http default { any; }; recursion yes; dnssec-validation auto; forwarders port 853 { 1.1.1.1 tls cloudflare-tls; 1.0.0.1 tls cloudflare-tls; 8.8.8.8 tls google-tls; 8.8.4.4 tls google-tls; }; forward only; dns64 <IPv6_nat64_NSP>/96 { clients { any; }; exclude { <IPv6_nat64_NSP>/96; ::ffff:0:0/96; }; mapped { !RFC1918; any; }; }; }; -
A big
for the public NAT64 Prefix providers
! -
Why
fullchain.pemand notcert.pemincert-file? find out yourself
!#!/bin/bash openssl s_client -connect <DOMAINS>:853 -showcerts
Ciao Adios!